﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Web;
using Powersource.WebApp.Model;
using Powersource.WebApp.Properties;


namespace Powersource.WebApp.Services
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "RemoteBuildBridge" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select RemoteBuildBridge.svc or RemoteBuildBridge.svc.cs at the Solution Explorer and start debugging.
    public class RemoteBuildBridge : IRemoteBuildBridge, IDisposable
    {

        const string CNR_BaseDropLocation = "{BaseDropLocation}";
        const string CNR_BuildToken = "{build_token}";
        const string CNR_BuildDefinition = "{buildDefinition}";
        const string CNR_BuildNumber = "{buildNumber}";

        string FullDropLocationFormatString = CNR_BaseDropLocation + "\\" + CNR_BuildToken + "\\" + CNR_BuildDefinition + "\\" + CNR_BuildNumber + "\\";
        internal DirectoryInfo ZipFileLocation;

        EventLogWriter Log;

        public Queue<Stream> StreamsToDispose;

        private void ConfigureZipLocation()
        {
            if (ZipFileLocation == null)
                ZipFileLocation = new DirectoryInfo(HttpContext.Current.Server.MapPath("~/Download/"));
            
        }

        public BuildBridgeConfiguration GetConfiguration()
        {
            var config = new BuildBridgeConfiguration();
            config.UNCPathBase = Settings.Default.BaseDropLocation;
            return config;
        }

        public Stream GetRemoteBuild(Guid build_token, string buildDefinition, string buildNumber)
        {
            EnsureLog();
            var buildLogString = string.Format("Build {0} [{1}, {2}]", build_token, buildDefinition, buildNumber);
            Log.EntryAppend = buildLogString;
            try
            {
                if (build_token == Guid.Empty || build_token == null) throw new ArgumentException("Build token cannot be empty");
                if (string.IsNullOrWhiteSpace(buildDefinition)) throw new ArgumentException("Invalid buildDefinition. It cannot be empty or null.");
                if (string.IsNullOrWhiteSpace(buildNumber)) throw new ArgumentException("Invalid buildNumber. It cannot be empty or null.");
                

                var fdlsb = new StringBuilder(FullDropLocationFormatString);
                fdlsb.Replace(CNR_BaseDropLocation, Settings.Default.BaseDropLocation);
                fdlsb.Replace(CNR_BuildToken, build_token.ToString());
                fdlsb.Replace(CNR_BuildDefinition, buildDefinition);
                fdlsb.Replace(CNR_BuildNumber, buildNumber);
                fdlsb.Replace("\\\\", "\\", 2, fdlsb.Length - 2);
                fdlsb.Replace("..", "");
                string FullDropLocation = fdlsb.ToString();
                if (!Directory.Exists(FullDropLocation)) throw new InvalidOperationException(string.Format("Cannot find the build directory for {0}", buildLogString));
                

                FileInfo compressedFile = null;
                //Compression 
                try
                {
                    var download_Guid = Guid.NewGuid();
                    if (ZipFileLocation == null) ConfigureZipLocation();
                    FileInfo fi = new FileInfo(Path.Combine(ZipFileLocation.FullName, download_Guid.ToString()));
                    ZipFile.CreateFromDirectory(FullDropLocation, fi.FullName, CompressionLevel.Optimal, false);
                    compressedFile = fi;
                }
                catch (Exception exc)
                {
                    var r = string.Format("Cannot send the zipped file as compression of target directory failed for build [{0}]", buildLogString);
                    Log.Error(exc, r);
                    throw new InvalidOperationException(r);
                }
                if (compressedFile == null) throw new InvalidOperationException("Internal error - compressed file just created cannot be find. ");
                //Reading and sending
                Stream s = File.Open(compressedFile.FullName, FileMode.Open, FileAccess.Read);
                AddStreamToBeDisposedOnDispose(s);
                return s;
            }
            catch (ArgumentException aex)
            {
                Log.Warn(aex, "GetRemoteBuild failed because one of the argument provided was not in expected format/value");
                throw;
            }
            catch (Exception exc)
            {
                Log.Error(exc, "GetRemoteBuild failed because of an unexpected error. Details are below. ");
                throw new InvalidOperationException("Internal error while preparing builded zip file. Check server logs for details.");
            }
        }

        private void AddStreamToBeDisposedOnDispose(Stream s)
        {
            if (StreamsToDispose == null) StreamsToDispose = new Queue<Stream>();
            StreamsToDispose.Enqueue(s);
        }

        private void EnsureLog()
        {
            if (Log == null) Log = new EventLogWriter();
        }



        public bool IsDisposed
        {
            get;
            private set;
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private void Dispose(bool p)
        {
            if (p && !IsDisposed)
            {
                IsDisposed = true;
                if (StreamsToDispose != null)
                {
                    foreach (var s in StreamsToDispose)
                    {
                        try
                        {
                            if (s != null) s.Dispose();
                        }
                        catch (Exception exc)
                        {
                            if (Log != null) Log.Error(exc, "Stream not disposed properly when service is to be disposed, possible memory leak");
                        }
                    }
                }
                if (Log != null) { Log.Dispose(); Log = null; }
            }
        }
    }
}

